﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SharpAnalyzer.Entities;
using SharpAnalyzer.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace SharpAnalyzer.Analyzers
{
    internal class HalsteadAnalyzer : CSharpSyntaxWalker
    {
        private HalsteadMetrics metrics;

        public HalsteadAnalyzer()
          : base(SyntaxWalkerDepth.Node)
        {
        }

        public HalsteadMetrics Calculate(Member node)
        {
            Verify.NotNull(() => node);

            BlockSyntax body = MemberBodySelector.FindBody(node);
            if (body != null)
            {
                Visit(body);
                return metrics;
            }
            if (CalculateGenericPropertyMetrics(node))
                return metrics;

            return new HalsteadMetrics();
        }

        private bool CalculateGenericPropertyMetrics(Member node)
        {
            PropertyDeclarationSyntax declarationSyntax;
            if ((declarationSyntax = node.SyntaxNode as PropertyDeclarationSyntax) != null)
            {
                bool flag = declarationSyntax.Modifiers.Any(x => x.ValueText == "static");

                if (MemberBodySelector.FindBody(node) == null)
                {
                    switch (node.Kind)
                    {
                        case MemberKind.GetProperty:
                            metrics = flag ? HalsteadMetrics.GenericStaticGetPropertyMetrics : HalsteadMetrics.GenericInstanceGetPropertyMetrics;
                            return true;
                        case MemberKind.SetProperty:
                            metrics = flag ? HalsteadMetrics.GenericStaticSetPropertyMetrics : HalsteadMetrics.GenericInstanceSetPropertyMetrics;
                            return true;
                    }
                }
            }
            return false;
        }

        public override void VisitBlock(BlockSyntax node)
        {
            base.VisitBlock(node);

            // https://en.wikipedia.org/wiki/Halstead_complexity_measures
            IEnumerable<SyntaxToken> tokens = node.DescendantTokens(); // (Func<SyntaxNode, bool>)null, false);
            IDictionary<SyntaxKind, IList<string>> dicOperands = ParseTokens(tokens, HalsteadOperands.All);
            IDictionary<SyntaxKind, IList<string>> dicOperators = ParseTokens(tokens, HalsteadOperators.All);
            var operands = from oprList in dicOperands.Values
                           from opr in oprList
                           select opr;
            var operators = from oprList in dicOperators.Values
                            from opr in oprList
                            select opr;

            metrics = new HalsteadMetrics()
            {
                NumOperands = operands.Count(),
                NumUniqueOperands = operands.Distinct().Count(),

                NumOperators = operators.Count(),
                NumUniqueOperators = operators.Distinct().Count(),
            };
        }

        private static IDictionary<SyntaxKind, IList<string>> ParseTokens(IEnumerable<SyntaxToken> tokens,
            IEnumerable<SyntaxKind> filter)
        {
            IDictionary<SyntaxKind, IList<string>> dictionary = new Dictionary<SyntaxKind, IList<string>>();
            foreach (SyntaxToken token in tokens)
            {
                // Take a token and see if it fits ANY of the filters
                SyntaxKind kind = token.Kind();
                if (filter.Any(x => x == kind))
                {
                    string valueText = token.ValueText;
                    IList<string> list;
                    if (!dictionary.TryGetValue(kind, out list))
                    {
                        dictionary[kind] = new List<string>();
                        list = dictionary[kind];
                    }
                    list.Add(valueText);
                }
            }
            return dictionary;
        }
    }

    internal sealed class HalsteadOperands
    {
        public static readonly IEnumerable<SyntaxKind> All = new SyntaxKind[125]
        {
            SyntaxKind.IdentifierToken,
            SyntaxKind.StringLiteralToken,
            SyntaxKind.NumericLiteralToken,
            SyntaxKind.AddKeyword,
            SyntaxKind.AliasKeyword,
            SyntaxKind.AscendingKeyword,
            SyntaxKind.AsKeyword,
            SyntaxKind.AsyncKeyword,
            SyntaxKind.AwaitKeyword,
            SyntaxKind.BaseKeyword,
            SyntaxKind.BoolKeyword,
            SyntaxKind.BreakKeyword,
            SyntaxKind.ByKeyword,
            SyntaxKind.ByteKeyword,
            SyntaxKind.CaseKeyword,
            SyntaxKind.CatchKeyword,
            SyntaxKind.CharKeyword,
            SyntaxKind.CheckedKeyword,
            SyntaxKind.ChecksumKeyword,
            SyntaxKind.ClassKeyword,
            SyntaxKind.ConstKeyword,
            SyntaxKind.ContinueKeyword,
            SyntaxKind.DecimalKeyword,
            SyntaxKind.DefaultKeyword,
            SyntaxKind.DefineKeyword,
            SyntaxKind.DelegateKeyword,
            SyntaxKind.DescendingKeyword,
            SyntaxKind.DisableKeyword,
            SyntaxKind.DoKeyword,
            SyntaxKind.DoubleKeyword,
            SyntaxKind.ElifKeyword,
            SyntaxKind.ElseKeyword,
            SyntaxKind.EndIfKeyword,
            SyntaxKind.EndRegionKeyword,
            SyntaxKind.EnumKeyword,
            SyntaxKind.EqualsKeyword,
            SyntaxKind.ErrorKeyword,
            SyntaxKind.EventKeyword,
            SyntaxKind.ExplicitKeyword,
            SyntaxKind.ExternKeyword,
            SyntaxKind.FalseKeyword,
            SyntaxKind.FieldKeyword,
            SyntaxKind.FinallyKeyword,
            SyntaxKind.FixedKeyword,
            SyntaxKind.FloatKeyword,
            SyntaxKind.ForEachKeyword,
            SyntaxKind.ForKeyword,
            SyntaxKind.FromKeyword,
            SyntaxKind.GetKeyword,
            SyntaxKind.GlobalKeyword,
            SyntaxKind.GotoKeyword,
            SyntaxKind.GroupKeyword,
            SyntaxKind.HiddenKeyword,
            SyntaxKind.IfKeyword,
            SyntaxKind.ImplicitKeyword,
            SyntaxKind.InKeyword,
            SyntaxKind.InterfaceKeyword,
            SyntaxKind.InternalKeyword,
            SyntaxKind.IntKeyword,
            SyntaxKind.IntoKeyword,
            SyntaxKind.IsKeyword,
            SyntaxKind.JoinKeyword,
            SyntaxKind.LetKeyword,
            SyntaxKind.LineKeyword,
            SyntaxKind.LockKeyword,
            SyntaxKind.LongKeyword,
            SyntaxKind.MakeRefKeyword,
            SyntaxKind.MethodKeyword,
            SyntaxKind.ModuleKeyword,
            SyntaxKind.NamespaceKeyword,
            SyntaxKind.NullKeyword,
            SyntaxKind.ObjectKeyword,
            SyntaxKind.OnKeyword,
            SyntaxKind.OperatorKeyword,
            SyntaxKind.OrderByKeyword,
            SyntaxKind.OutKeyword,
            SyntaxKind.OverrideKeyword,
            SyntaxKind.ParamKeyword,
            SyntaxKind.ParamsKeyword,
            SyntaxKind.PartialKeyword,
            SyntaxKind.PragmaKeyword,
            SyntaxKind.PrivateKeyword,
            SyntaxKind.PropertyKeyword,
            SyntaxKind.ProtectedKeyword,
            SyntaxKind.PublicKeyword,
            SyntaxKind.ReadOnlyKeyword,
            SyntaxKind.ReferenceKeyword,
            SyntaxKind.RefKeyword,
            SyntaxKind.RefTypeKeyword,
            SyntaxKind.RefValueKeyword,
            SyntaxKind.RegionKeyword,
            SyntaxKind.RemoveKeyword,
            SyntaxKind.RestoreKeyword,
            SyntaxKind.ReturnKeyword,
            SyntaxKind.SByteKeyword,
            SyntaxKind.SealedKeyword,
            SyntaxKind.SelectKeyword,
            SyntaxKind.SetKeyword,
            SyntaxKind.ShortKeyword,
            SyntaxKind.SizeOfKeyword,
            SyntaxKind.StackAllocKeyword,
            SyntaxKind.StaticKeyword,
            SyntaxKind.StringKeyword,
            SyntaxKind.StructKeyword,
            SyntaxKind.SwitchKeyword,
            SyntaxKind.ThisKeyword,
            SyntaxKind.TrueKeyword,
            SyntaxKind.TryKeyword,
            SyntaxKind.TypeKeyword,
            SyntaxKind.TypeOfKeyword,
            SyntaxKind.TypeVarKeyword,
            SyntaxKind.UIntKeyword,
            SyntaxKind.ULongKeyword,
            SyntaxKind.UncheckedKeyword,
            SyntaxKind.UndefKeyword,
            SyntaxKind.UnsafeKeyword,
            SyntaxKind.UShortKeyword,
            SyntaxKind.UsingKeyword,
            SyntaxKind.VirtualKeyword,
            SyntaxKind.VoidKeyword,
            SyntaxKind.VolatileKeyword,
            SyntaxKind.WarningKeyword,
            SyntaxKind.WhereKeyword,
            SyntaxKind.WhileKeyword,
            SyntaxKind.YieldKeyword
        };
    }

    internal sealed class HalsteadOperators
    {
        public static readonly IEnumerable<SyntaxKind> All = new SyntaxKind[25]
        {
            SyntaxKind.DotToken,
            SyntaxKind.EqualsToken,
            SyntaxKind.SemicolonToken,
            SyntaxKind.PlusPlusToken,
            SyntaxKind.PlusToken,
            SyntaxKind.PlusEqualsToken,
            SyntaxKind.MinusMinusToken,
            SyntaxKind.MinusToken,
            SyntaxKind.MinusEqualsToken,
            SyntaxKind.AsteriskToken,
            SyntaxKind.AsteriskEqualsToken,
            SyntaxKind.SlashToken,
            SyntaxKind.SlashEqualsToken,
            SyntaxKind.PercentToken,
            SyntaxKind.PercentEqualsToken,
            SyntaxKind.AmpersandToken,
            SyntaxKind.BarToken,
            SyntaxKind.CaretToken,
            SyntaxKind.TildeToken,
            SyntaxKind.ExclamationToken,
            SyntaxKind.ExclamationEqualsToken,
            SyntaxKind.GreaterThanToken,
            SyntaxKind.GreaterThanEqualsToken,
            SyntaxKind.LessThanToken,
            SyntaxKind.LessThanEqualsToken
        };
    }
}
